package model;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketTimeoutException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;
import controller.Link;
import model.User;
import model.game.World;
import controller.link.Ask4Yourself;
import controller.link.RefreshGameData;
import controller.link.Register;
import controller.link.Yell;
import view.server.Console;

public class Server implements Runnable {

    private ServerSocket server;
    private ArrayList<Attendant> attendants;
    private ArrayList<Battle> battles;
    private boolean initialized;
    private boolean running;
    private Thread thread;
    private World world;
    private ArrayList<User> users;
    private int codgen;
    private Console csl;
    private User user;
    private Register register;

    public Server(int port, Console csl) throws Exception {
        attendants = new ArrayList<Attendant>();
        initialized = false;
        running = false;
        battles = new ArrayList<Battle>();
        users = new ArrayList<User>();
        this.csl = csl;
        world = new World(this);        
        open(port);
    }

    private void open(int port) throws Exception {
        server = new ServerSocket(port);
        initialized = true;
    }

    private void close() {
        world.save();
        for (Attendant attendant : attendants) {
            try {
                attendant.stop();
            } catch (Exception e) {
                this.sendToConsole(e.getMessage());
            }
        }
        try {
            server.close();
        } catch (Exception e) {
            this.sendToConsole(e.getMessage());
        }
        server = null;
        initialized = false;
        running = false;
        thread = null;
        try {
            stop();
        } catch (Exception ex) {
        }
    }

    public void addBattle(Battle mg) {
        battles.add(mg);
    }

    public void removeBattle(Battle mg) {
        battles.remove(mg);
    }

    public Battle getBattle(Attendant at) {
        for (int i = 0; i < battles.size(); i++) {
            if (battles.get(i).isBattling(at)) {
                return battles.get(i);
            }
        }
        return null;
    }
    public void notifyAllClients(){
        for (int i=0; i<attendants.size(); i++){
            new RefreshGameData().serverUse(this, attendants.get(i));
        }
    }
    public ArrayList<Attendant> getAttendants() {
        return attendants;
    }

    public void sendMsgToAll(String str) throws Exception {
        Iterator<Attendant> i = attendants.iterator();
        while (i.hasNext()) {
            Attendant attendant = i.next();
            try {
                Yell yell = new Yell("Nobody", str);
                attendant.send((Link)yell);
                this.sendToConsole(str);
            } catch (Exception e) {
                this.sendToConsole(e.getMessage());
                attendant.stop();
                i.remove();
            }
        }
    }

    public void sendToAll(Object objeto) throws Exception {
        Iterator<Attendant> i = attendants.iterator();
        while (i.hasNext()) {
            Attendant attendant = i.next();
            try {
                attendant.send(objeto);
            } catch (Exception e) {
                System.out.println(e.getMessage());
                attendant.stop();
                i.remove();
            }
        }
    }

    public void sendToConsole(String str) {
        csl.outputText(str);
    }
    
    public void addUser(User user){
        users.add(user);
        csl.addUser(user);
    }

    public void removeUser(User user){
        users.remove(user);
        csl.removeUser(user.getName());
    }
    
    
    public ArrayList<User> getUsers(){
        return users;
    }
    
    public ArrayList<User> getUsers(User user){
        ArrayList<User> newUsers = new ArrayList<User>();
        for (int it = 0; it < users.size(); it++) {
            if (user != users.get(it)) {
                newUsers.add(users.get(it));
            }
        }
        return newUsers;
    }

    public User findUsers(String name) {
        for (int i = 0; i < users.size(); i++) {
            if (users.get(i).getName().equals(name)) {
                return users.get(i);
            }
        }
        return null;
    }
    
    public void removeAtendente(Attendant attendant) {
        for (int it = 0; it < attendants.size(); it++) {
            if (attendant.equals(attendants.get(it))) {
                attendants.remove(it);
            }
        }
    }
    public World getWorld(){
        return world;
    }
    public void codGenerator(User user) {
        if (user.getId() == 0) {
            user.setId(codgen);
            codgen++;
        }
    }

    public void sendTo(String name, Object objeto) {
        searchAtendente(name).send(objeto);
    }

    public void kickPlayer(String name) {
        Attendant attendant = this.searchAtendente(name);
        if (attendant != null) {
            this.sendMsg("Você foi kickado pelo servidor", attendant);
            try {
                attendant.stop();
                csl.removeUser(name);
            } catch (Exception e) {
                this.sendToConsole(e.getMessage());

            }
        } else {
            this.sendToConsole("Usuário não existente");
        }
    }

    public void sendMsg(String str, Attendant attendant) {
        Yell yell = new Yell(str);
        yell.serverUse(this, attendant);
    }

    public Attendant searchAtendente(String name) {
        for (int i = 0; i < attendants.size(); i++) {
            if (attendants.get(i).getUser().getName().toLowerCase().equals(name.toLowerCase())) {
                return attendants.get(i);
            }
        }
        return null;
    }

    public boolean testName(String nome) {
        for (int i = 0; i < getUsers().size(); i++) {
            String comp = getUsers().get(i).getName().toString();
            if (comp.equals(nome)) {
                return false;  //Caso o nickname já exista retornará false                      
            } //if                                
        } //for
        return true; //Caso não exista nenhuma nome igual ao inserido retornará true
    }

    public void start() {
        if (!initialized || running) {
            return;
        }
        running = true;
        codgen = 1;
        thread = new Thread(this);
        thread.start();
    }

    public void stop() throws Exception {
        running = false;
        Yell yell = new Yell("Desligando servidor, você foi desconectado");
        this.sendToAll((Link) yell);
        //this.sendToAll(new Message(3,"Desligando servidor, você foi desconectado"));
        if (thread != null) {
            thread.join();
        }
    }
    
    public synchronized void sendOnAutentication(ObjectOutputStream out, Object objeto){
            try {
                out.writeObject(objeto);
                out.flush();
                out.reset();
            } catch (IOException ex) {
                Logger.getLogger(Attendant.class.getName()).log(Level.SEVERE, null, ex);
                close();
            }
    }
    public void everyoneNeedPlayers(int code){
        for (int i=0; i< attendants.size(); i++){
            new Ask4Yourself(this, attendants.get(i));
        }
    }
    public int autentication(Socket socket, ObjectInputStream in, ObjectOutputStream out) throws IOException, ClassNotFoundException{
        Object mensagem = in.readObject();
        if (mensagem instanceof Link){
            Link link = (Link)mensagem;
            link.serverUse(this, null);
            if (register!=null){
                sendOnAutentication(out, register);
                return 1;
            }else{
                return 0;
            }
            
        }
        return 0;
    }
    
    @Override
    public void run() {
        if (csl.isVisible()){
            this.sendToConsole("Server aberto");
        }        
        while (running) {
            try {
                server.setSoTimeout(2500);
                Socket socket = server.accept();
                ObjectInputStream in = new ObjectInputStream(socket.getInputStream());
                ObjectOutputStream out = new ObjectOutputStream(socket.getOutputStream());
                if (autentication(socket, in, out)==1){
                    Attendant attendant = new Attendant(this, socket, in, out, user);
                    if (attendant.getUser() != null) {
                        attendant.start();
                        this.codGenerator(attendant.getUser());
                        attendants.add(attendant);
                        notifyAllClients();
                    }
                }             
                
            } catch (SocketTimeoutException e) {
                //ignorar
            } catch (Exception e) {
                this.sendToConsole(e.getMessage());
                break;
            }
        }
        close();
    }

    public void setUser(User user) {
        this.user = user;
    }

    public Register getRegister() {
        return register;
    }

    public void setRegister(Register register) {
        this.register = register;
    }
}